package org.eclipse.iota.bounds;

import static org.eclipse.iota.bounds.BoundType.LOWER;
import static org.eclipse.iota.bounds.BoundType.UPPER;

import org.eclipse.iota.model.Addition;
import org.eclipse.iota.model.Constant;
import org.eclipse.iota.model.Division;
import org.eclipse.iota.model.Exponentiation;
import org.eclipse.iota.model.Multiplication;
import org.eclipse.iota.model.Negation;
import org.eclipse.iota.model.Operation;

/**
 * Static arithmetical methods for combining bounds.
 * @author Christian Krause
 * @generated NOT
 */
public class IotaBoundCalculator {

	/**
	 * Negate a bound. This also flips its bound type.
	 * @param bound Bound to be negated.
	 * @return The negated bound.
	 */
	public static Bound negate(Bound bound) {
		return new Bound(bound.getValue().copy().negate(), bound.getType().flip());
	}
	
	/**
	 * Try to add two bounds. Returns <code>null</code> 
	 * if the bounds are not compatible.
	 * @param b1 Bound 1.
	 * @param b2 Bound 2.
	 * @return The resulting bound or <code>null</code>.
	 */
	public static Bound add(Bound b1, Bound b2) {
		if (b1.getType()==b2.getType()) {
			return new Bound(b1.getValue().copy().add(b2.getValue()), b1.getType());
		} else {
			return null;
		}
	}
	
	/**
	 * Try to multiply two bounds. Returns <code>null</code> 
	 * if the bounds are not compatible.
	 * @param b1 Bound 1.
	 * @param b2 Bound 2.
	 * @return The resulting bound or <code>null</code>.
	 */
	public static Bound multiply(Bound b1, Bound b2) {
		
		// Check if they can be multiplied:
		if (!canMultiply(b1) || !canMultiply(b2)) return null;
		
		// Compute the bound and the bound type:
		Constant multiplied = b1.getValue().copy().multiply(b2.getValue());
		BoundType type = (b1.getType()==b2.getType()) ? LOWER : UPPER;
		
		return new Bound(multiplied,type);
		
	}
	
	/*
	 * Check whether a bound can be multiplied with another.
	 */
	private static boolean canMultiply(Bound bound) {
		
		// Lower bounds must be not negative.
		if (bound.isLower()) return !bound.getValue().isNegative();
		
		// Upper bounds must be not positive.
		if (bound.isUpper()) return !bound.getValue().isPositive();
		
		// Empty bound type? (should not occur)
		return false;
		
	}
	
	/**
	 * Try to divide a bound by another bound. Returns <code>null</code> 
	 * if the bounds are not compatible.
	 * @param b1 Bound 1.
	 * @param b2 Bound 2.
	 * @return The resulting bound or <code>null</code>.
	 */
	public static Bound divide(Bound b1, Bound b2) {
		// TODO dividing of bounds
		return null;
	}

	
	/**
	 * Take the power of a bound.
	 * @param base Base bound.
	 * @param exponent Exponent bound.
	 * @return Resulting bound or <code>null</code>.
	 */
	public static Bound pow(Bound base, Bound exponent) {
		
		if (base.isLower() && base.getValue().isPositive() &&
			exponent.isLower() && exponent.getValue().isPositive() && 
			exponent.getValue().isInteger()) {
			
			// Compute the new lower bound.
			return new Bound(base.getValue().copy().pow(exponent.getValue().getValue().intValue()), LOWER);
		}
		
		// Not compatible.
		return null;
		
	}
	
	/**
	 * Perform an operation on two given bounds.
	 * @param type Operation type.
	 * @param b1 Bound 1.
	 * @param b2 Bound 2.
	 * @return Resulting bound or <code>null</code>.
	 */
	public static Bound performOperation(Class<? extends Operation> type, Bound b1, Bound b2) {
		if (type==Addition.class) return add(b1,b2);
		if (type==Multiplication.class) return multiply(b1,b2);
		if (type==Division.class) return divide(b1,b2);
		if (type==Exponentiation.class) return pow(b1,b2);
		if (type==Negation.class) return negate(b1);
		return null;
	}

}
